﻿@page "/earc/editor"
@page "/earc/editor/{ModId}"
@using Microsoft.EntityFrameworkCore
@using Flagrum.Web.Persistence.Entities
@using Flagrum.Web.Persistence.Entities.ModManager
@using System.Timers
@using Flagrum.Web.Features.ModManager.Modals
@using System.IO
@using System.Diagnostics
@using Flagrum.Core.Utilities.Types
@inherits ModComponentBase

@inject NavigationManager Navigation
@inject IWpfService WpfService
@inject ILogger<Editor> Logger
@inject IStringLocalizer<Editor> Localizer
@inject ProfileService Profile

<LoadingView IsLoading="IsLoading" Text="@LoadingText" CssClass="h-full">
    <div class="p-6 flex flex-row h-full">
        <div class="flex-grow h-full flex flex-col">
            <div class="box p-6 mb-6 flex flex-row items-center">
                <h4 class="text-grey-200 flex-grow">@Mod.Name</h4>
                <Button Icon="save" Text="@Localizer["Save"]" CssClass="mr-3" OnClickAsync="Save"/>
                @if (ModId != null && ModManagerService.HasAnyCachedFiles(Mod))
                {
                    <Button Icon="folder_off" Text="@Localizer["ClearCache"]" OnClick="ClearCache" CssClass="mr-3"/>
                }
                <Button Icon="delete_forever" Text="@Localizer["Delete"]" OnClick="Delete" CssClass="bg-error hover:bg-error mr-3"/>
                <Button Icon="cancel" Text="@Localizer["Cancel"]" OnClick="@(() => Navigation.NavigateTo("/"))"/>
            </div>
            <div class="box flex flex-row items-center p-6 border-b border-black">
                <h5 class="text-grey-200 flex-grow">@Localizer["BuildList"]</h5>
                <Button Icon="folder" Text="@Localizer["ReplaceAsset"]" CssClass="mr-3" OnClick="OpenReplaceModal"/>
                <Button Icon="block" Text="@Localizer["RemoveAsset"]" OnClick="OpenRemoveModal"/>
            </div>
            <div class="box flex flex-row items-center px-4 py-2 border-b border-black">
                <span class="material-icons text-accent1-200 mr-3">filter_alt</span>
                <EditForm Model="this" class="flex-grow">
                    <Textbox @bind-Value="FilterQuery" Size="Textbox.Variant.Stretch"/>
                </EditForm>
            </div>
            <div class="flex-grow box overflow-auto">
                @if (Mod.Earcs.Any(e => e.Type == EarcChangeType.Change && e.Files.Any(f => f.Type is EarcFileChangeType.Remove or EarcFileChangeType.Replace)))
                {
                    <Virtualize Items="Mod.Earcs.Where(e => e.Type == EarcChangeType.Change && (FilterQuery == null || e.EarcRelativePath.Contains(FilterQuery, StringComparison.OrdinalIgnoreCase) || e.Files.Any(f => f.Type is EarcFileChangeType.Remove or EarcFileChangeType.Replace && f.Uri.Contains(FilterQuery, StringComparison.OrdinalIgnoreCase))) && e.Files.Any(f => f.Type is EarcFileChangeType.Remove or EarcFileChangeType.Replace)).ToList()"
                                Context="earc">
                        <div class="row border-b border-black px-4 py-3">
                            <span class="material-icons mr-3 cursor-pointer select-none"
                                  @onclick="() => { earc.IsExpanded = !earc.IsExpanded; StateHasChanged(); }">
                                @(earc.IsExpanded ? "expand_more" : "chevron_right")
                            </span>
                            <h5 class="text-grey-300">@earc.EarcRelativePath</h5>
                            <span class="material-icons pl-1.5 pt-0.5 cursor-pointer text-accent1-200 text-sm pb-0.5" @onclick="() => LaunchRelativePath(earc.EarcRelativePath)">
                                open_in_new
                            </span>
                        </div>

                        @if (earc.IsExpanded)
                        {
                            if (earc.Files.Count < 1)
                            {
                                <div class="px-12 py-4 border-b border-black">
                                    @Localizer["NoFileChangesSet"]
                                </div>
                            }

                            <Virtualize Items="earc.Files.Where(f => f.Type is EarcFileChangeType.Remove or EarcFileChangeType.Replace && (FilterQuery == null || f.Uri.Contains(FilterQuery, StringComparison.OrdinalIgnoreCase))).ToList()" Context="replacement">
                                <div class="flex flex-row items-center px-4 py-2 border-b border-black">
                                    <div class="text-right mx-4" style="flex: 0 0 120px;">
                                        @switch (replacement.Type)
                                        {
                                            case EarcFileChangeType.Replace:
                                                <strong>@Localizer["Replace"]:</strong>
                                                <br/>
                                                <strong>@Localizer["With"]:</strong>
                                                break;
                                            case EarcFileChangeType.Remove:
                                                <strong>@Localizer["Remove"]:</strong>
                                                break;
                                            case EarcFileChangeType.AddReference:
                                                <strong>@Localizer["AddReference"]:</strong>
                                                break;
                                            case EarcFileChangeType.Add:
                                                <strong>@Localizer["AddFile"]:</strong>
                                                <br/>
                                                <strong>@Localizer["From"]:</strong>
                                                break;
                                            case EarcFileChangeType.AddToTextureArray:
                                                <strong>Add To:</strong>
                                                <br/>
                                                <strong>@Localizer["From"]:</strong>
                                                break;
                                        }
                                    </div>
                                    <div style="flex: 1 1 auto;">
                                        @switch (replacement.Type)
                                        {
                                            case EarcFileChangeType.Replace:
                                            case EarcFileChangeType.Add:
                                            case EarcFileChangeType.AddToTextureArray:
                                                <div class="truncate-middle" style="max-width: calc(100vw - 820px)">
                                                    <div>@string.Join('/', replacement.Uri.Split('/')[..^1])</div>
                                                    <div>/@replacement.Uri.Split('/').Last()</div>
                                                </div>
                                                <br/>
                                                <div class="truncate-middle" style="max-width: calc(100vw - 820px)">
                                                    <div>@string.Join('\\', replacement.ReplacementFilePath.Split('\\')[..^1])</div>
                                                    <div>\@replacement.ReplacementFilePath.Split('\\').Last()</div>
                                                    <span class="material-icons pl-1.5 pt-0.5 cursor-pointer text-accent1-200 text-sm pb-0.5" @onclick="() => LaunchAbsolutePath(replacement.ReplacementFilePath)">
                                                        open_in_new
                                                    </span>
                                                </div>
                                                break;
                                            case EarcFileChangeType.Remove:
                                            case EarcFileChangeType.AddReference:
                                                <div class="truncate-middle" style="max-width: calc(100vw - 820px)">
                                                    <div>@string.Join('/', replacement.Uri.Split('/')[..^1])</div>
                                                    <div>/@replacement.Uri.Split('/').Last()</div>
                                                </div>
                                                break;
                                        }
                                    </div>
                                    <div class="ml-8">
                                        @if (replacement.Type is EarcFileChangeType.Add or EarcFileChangeType.Replace or EarcFileChangeType.AddToTextureArray)
                                        {
                                            <span class="material-icons text-accent1-200 cursor-pointer mr-3" @onclick="() => UpdateReplacementPath(replacement)">edit</span>
                                        }
                                        <span class="material-icons text-accent1-200 cursor-pointer" @onclick="() => RemoveReplacement(earc, replacement)">delete</span>
                                    </div>
                                </div>
                            </Virtualize>
                        }
                    </Virtualize>
                }

                @if (!Mod.Earcs.Any())
                {
                    <div class="rounded-md bg-accent1-900 border border-accent1-500 text-accent1-300 p-4 mt-4 mx-4">
                        @Localizer["NoFileChangesSet"]
                    </div>
                }
            </div>
        </div>

    </div>
</LoadingView>

<CascadingValue Value="this">
    <UriSelectModal @ref="Modal" OnFileSelected="OnFileSelected"/>
</CascadingValue>

<AlertModal @ref="Alert"/>
<PromptModal @ref="Prompt"/>

@code
{
    private readonly Timer _timer = new(300);

    [Parameter]
    public string ModId { get; set; }

    private bool IsLoading { get; set; }
    private string LoadingText { get; set; }
    private UriSelectModal Modal { get; set; }
    private AlertModal Alert { get; set; }
    private EarcFileChangeType ModalType { get; set; }
    private bool HasChanged { get; set; }

    private string _filterQuery;

    private string FilterQuery
    {
        get => _filterQuery;
        set
        {
            _timer.Stop();
            _filterQuery = value;
            _timer.Start();
        }
    }

    protected override async Task OnInitializedAsync()
    {
        _timer.Elapsed += (_, _) =>
        {
            InvokeAsync(StateHasChanged);
            _timer.Stop();
        };

        Mod = new EarcMod();
        IsLoading = true;
        LoadingText = "Loading Build List";
        StateHasChanged();

        await Task.Run(() =>
        {
            var modId = Convert.ToInt32(ModId);
            Mod = Context.EarcMods
                .Include(e => e.Earcs)
                .ThenInclude(e => e.Files)
                .Include(m => m.LooseFiles)
                .Where(m => m.Id == modId)
                .AsNoTracking()
                .ToList()
                .FirstOrDefault()!;
        });

        IsLoading = false;
        StateHasChanged();
    }

    private void OpenReplaceModal()
    {
        ModalType = EarcFileChangeType.Replace;
        Modal.Open();
    }

    private void OpenRemoveModal()
    {
        ModalType = EarcFileChangeType.Remove;
        Modal.Open();
    }

    private async Task OnFileSelected(string uri)
    {
        if (Mod.Earcs.Any(e => e.Files.Any(r => r.Uri.Equals(uri, StringComparison.OrdinalIgnoreCase))))
        {
            Alert.Open("Warning", "Invalid Action", "You cannot alter the same file twice.", null);
            return;
        }

        const string filter = "All Files|*.*";

        switch (ModalType)
        {
            case EarcFileChangeType.Replace:
            {
                await WpfService.OpenFileDialogAsync(filter, async file =>
                {
                    Mod.AddReplacement(Context.GetArchiveRelativeLocationByUri(uri), uri, file);
                    await InvokeAsync(() =>
                    {
                        Modal.Close();
                        StateHasChanged();
                    });
                });
                break;
            }
            case EarcFileChangeType.Remove:
                Mod.AddRemoval(Context.GetArchiveRelativeLocationByUri(uri), uri);
                await InvokeAsync(() =>
                {
                    Modal.Close();
                    StateHasChanged();
                });
                break;
        }

        HasChanged = true;
    }

    private void RemoveReplacement(EarcModEarc earc, EarcModFile file)
    {
        earc.Files.Remove(file);

        if (earc.Files.Count == 0)
        {
            Mod.Earcs.Remove(earc);
        }

        HasChanged = true;
        StateHasChanged();
    }

    private async Task UpdateReplacementPath(EarcModFile file)
    {
        const string filter = "All Files|*.*";
        await WpfService.OpenFileDialogAsync(filter, path =>
        {
            file.ReplacementFilePath = path;
            HasChanged = true;
            InvokeAsync(StateHasChanged);
        });
    }

    private async Task Save()
    {
        if (Context.Profile.IsGameRunning())
        {
            Alert.Open("Error", "FFXV is Running", "Flagrum cannot apply mods while the game is running. Please save and close down FFXV, then try again.", null);
            return;
        }

        LoadingText = "Saving Mod";
        IsLoading = true;
        StateHasChanged();

        if (HasChanged || Mod.HaveFilesChanged)
        {
            var deadFiles = Mod.Earcs.SelectMany(e => e.Files
                .Where(r => r.Type is EarcFileChangeType.Replace or EarcFileChangeType.Add or EarcFileChangeType.AddToTextureArray
                            && !File.Exists(r.ReplacementFilePath)))
                .Select(r => r.ReplacementFilePath)
                .ToList();

            if (deadFiles.Any())
            {
                var message = deadFiles.Aggregate("The mod could not be built as the following files were missing:<ul class='mt-4'>",
                    (current, file) => current + $"<li>{file}</li>");
                Alert.Open("Error", "Missing Files", message + "</ul>", null, 400, 300, true);
                IsLoading = false;
                StateHasChanged();
            }
            else
            {
                await Task.Run(async () => await CheckConflicts(Mod,
                    async () =>
                    {
                        var mod = Context.EarcMods
                            .Include(m => m.Earcs)
                            .ThenInclude(e => e.Files)
                            .Include(m => m.LooseFiles)
                            .Where(e => e.Id == Mod.Id)
                            .AsNoTracking()
                            .ToList()
                            .First();

                        if (Mod.IsActive)
                        {
                            ModManagerService.RevertMod(mod);
                        }

                        if (HasChanged)
                        {
                            await ModManagerService.SaveBuildList(Mod);
                        }

                        await ModManagerService.EnableMod(Mod.Id);

                        Navigation.NavigateTo("/");
                    },
                    async () =>
                    {
                        await ModManagerService.SaveBuildList(Mod);
                        Navigation.NavigateTo("/");
                    }));
            }
        }
        else
        {
            if (Mod.IsActive)
            {
                Navigation.NavigateTo("/");
            }
            else
            {
                await Task.Run(async () => await CheckConflicts(Mod,
                    async () =>
                    {
                        await ModManagerService.EnableMod(Mod.Id);
                        Navigation.NavigateTo("/");
                    },
                    () =>
                    {
                        Navigation.NavigateTo("/");
                        return Task.CompletedTask;
                    }));
            }
        }
    }

    private void Delete()
    {
        Prompt.Title = "Delete Mod";
        Prompt.Heading = "Are you sure?";
        Prompt.Subtext = "This action cannot be undone!";
        Prompt.YesText = "Delete";
        Prompt.NoText = "Cancel";
        Prompt.OnNo = null;
        Prompt.OnYes = async () => await InvokeAsync(async () =>
        {
            LoadingText = "Deleting Mod";
            IsLoading = true;
            StateHasChanged();

            await Task.Run(async () => await ModManagerService.DeleteMod(Mod.Id));
            Navigation.NavigateTo("/");
        });

        Prompt.Open();
    }

    private void ClearCache()
    {
        ModManagerService.ClearCachedFilesForMod(Mod.Id);
        Alert.Open("Success", "Cache Cleared", "The cached build files for this mod have been cleared.", null, 300, 200);
        StateHasChanged();
    }

    private void LaunchRelativePath(string relativePath)
    {
        var uri = $@"{Profile.GameDataDirectory}\{relativePath[..relativePath.LastIndexOf('\\')]}";
        Process.Start(new ProcessStartInfo(uri) {UseShellExecute = true});
    }

    private void LaunchAbsolutePath(string path)
    {
        Process.Start(new ProcessStartInfo(path) {UseShellExecute = true});
    }
}